// Copyright 2022 marcello<volibearw@gmail.com>. All rights reserved.

package main

import (
	"fmt"
	"log"
	"os"
	"strings"

	"gorm.io/driver/mysql"
	"gorm.io/gorm"
)

// connect 连接数据
func connect(param *CLIParam) (err error) {
	dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local",
		param.User,
		param.Passwd,
		param.Host,
		param.Port,
		param.Schema,
	)
	if Db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{}); err != nil {
		log.Println(err)
		return err
	}
	return nil
}

// queryAllTable 查询指定 schema 所有表和列信息
func queryAllTable(schema string) ([]Columns, error) {
	var columns []Columns
	var result = Db.Table("information_schema.columns").Find(&columns, map[string]interface{}{
		"TABLE_SCHEMA": schema,
	})
	if result.Error != nil {
		return nil, result.Error
	}
	return columns, nil
}

// toCamelString 蛇形转驼峰
func toCamelString(s string) string {
	data := make([]byte, 0, len(s))
	j := false
	k := false
	num := len(s) - 1
	for i := 0; i <= num; i++ {
		d := s[i]
		if k == false && d >= 'A' && d <= 'Z' {
			k = true
		}
		if d >= 'a' && d <= 'z' && (j || k == false) {
			d = d - 32
			j = false
			k = true
		}
		if k && d == '_' && num > i && s[i+1] >= 'a' && s[i+1] <= 'z' {
			j = true
			continue
		}
		data = append(data, d)
	}
	return string(data[:])
}

// toSnakeString 驼峰转蛇形
func toSnakeString(s string) string {
	data := make([]byte, 0, len(s)*2)
	j := false
	num := len(s)
	for i := 0; i < num; i++ {
		d := s[i]
		// or通过ASCII码进行大小写的转化
		// 65-90（A-Z），97-122（a-z）
		//判断如果字母为大写的A-Z就在前面拼接一个_
		if i > 0 && d >= 'A' && d <= 'Z' && j {
			data = append(data, '_')
		}
		if d != '_' {
			j = true
		}
		data = append(data, d)
	}
	//ToLower把大写字母统一转小写
	return strings.ToLower(string(data[:]))
}

// 返回字符串第一个转为小写的字母
func getFirstWordToLowercase(s string) string {
	if s == "" {
		return s
	}
	return strings.ToLower(s[0:1])
}

// getCamelStringFirstWordLowercase 转为驼峰并且第一个字母小写
func getCamelStringFirstWordLowercase(name string) string {
	if name == "" {
		return ""
	}
	name = toCamelString(name)
	return strings.ToLower(name[0:1]) + name[1:]
}

// mySQLDataType 类型转换
func mySQLDataType(t string) DataType {
	return DataType(strings.ToUpper(t))
}

// MySQLDataTypeToGo mysql数据类型转换为golang基础数据类型
func mySQLDataTypeToGo(srcDataType DataType, isUnsigned bool) DataType {
	switch srcDataType {
	case MySQLTypeTinyint, MySQLTypeSmallint, MySQLTypeMediumint, MySQLTypeInt, MySQLTypeInteger, MySQLTypeYear:
		// int32
		if isUnsigned {
			return GoTypeUint32
		}
		return GoTypeInt32
	case MySQLTypeBigint:
		// int64
		if isUnsigned {
			return GoTypeUint64
		}
		return GoTypeInt64
	case MySQLTypeFloat:
		// float32
		return GoTypeFloat32
	case MySQLTypeDouble, MySQLTypeDecimal:
		// float64
		return GoTypeFloat64
	case MySQLTypeDate, MySQLTypeDatetime, MySQLTypeTimestamp:
		// time.Time
		return GoTypeDateTime
	case MySQLTypeTinyblob, MySQLTypeBlob, MySQLTypeMediumblob, MySQLTypeLongblob:
		return GoTypeByteArr
	default:
		// MySQLTypeChar, MySQLTypeVarchar, MySQLTypeTinytext, MySQLTypeText, MySQLTypeMediumtext,
		// MySQLTypeLongtext, MySQLTypeTime
		return GoTypeString
	}
}

// mySQLDataTypeToProtobuf3 mysql数据类型转换为protobuf3基础数据类型
func mySQLDataTypeToProtobuf3(srcDataType DataType, isUnsigned bool) DataType {
	switch srcDataType {
	case MySQLTypeTinyint, MySQLTypeSmallint, MySQLTypeMediumint, MySQLTypeInt, MySQLTypeInteger, MySQLTypeYear:
		// int32
		if isUnsigned {
			return Protobuf3Uint32
		}
		return Protobuf3Int32
	case MySQLTypeBigint:
		// int64
		if isUnsigned {
			return Protobuf3Uint64
		}
		return Protobuf3Int64
	case MySQLTypeFloat:
		// float32
		return Protobuf3Float
	case MySQLTypeDouble, MySQLTypeDecimal:
		// float64
		return Protobuf3Float
	case MySQLTypeTinyblob, MySQLTypeBlob, MySQLTypeMediumblob, MySQLTypeLongblob:
		return Protobuf3Bytes
	default:
		// MySQLTypeChar, MySQLTypeVarchar, MySQLTypeTinytext, MySQLTypeText, MySQLTypeMediumtext,
		// MySQLTypeLongtext, MySQLTypeTime
		// MySQLTypeDate, MySQLTypeDatetime, MySQLTypeTimestamp
		return Protobuf3String
	}
}

// analysisColumns 将列、表数据提前分析出需要的数据格式
func analysisColumns(columns []Columns) map[string][]Columns {

	var result = make(map[string][]Columns)
	for _, val := range columns {
		val.TableNameFirstWordLowercase = getFirstWordToLowercase(val.TableName)
		val.TableNameCame = toCamelString(val.TableName)
		val.TableNameCameFirstWordLowercase = getCamelStringFirstWordLowercase(val.TableName)

		val.ColumnNameCame = toCamelString(val.ColumnName)
		val.ColumnNameSnake = toSnakeString(val.ColumnName)

		var isUnsigned = strings.Contains(strings.ToLower(val.ColumnType), "unsigned")
		val.ColumnProtoDataType = string(mySQLDataTypeToProtobuf3(mySQLDataType(val.DataType), isUnsigned))
		val.ColumnGoDataType = string(mySQLDataTypeToGo(mySQLDataType(val.DataType), isUnsigned))
		val.Extra = strings.ToLower(val.Extra)
		val.ColumnKey = strings.ToLower(val.ColumnKey)

		result[val.TableName] = append(result[val.TableName], val)
	}
	return result
}

// writeFile 写文件
func writeFile(filename, content string) (err error) {
	var f *os.File
	f, err = os.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC|os.O_APPEND, os.ModePerm)
	if err != nil {
		return
	}
	if _, err = f.WriteString(content); err != nil {
		return
	}
	if err = f.Close(); err != nil {
		return
	}
	return nil
}

// isGormDefaultModelIDField Gorm.Model 默认字段id
func isGormDefaultModelIDField(columnName string) bool {
	if columnName == "id" {
		return true
	}
	return false
}

// isGormDefaultModelDeletedField Gorm.Model 默认字段 DeletedAt
func isGormDefaultModelDeletedField(columnName string) bool {
	if columnName == "deleted_at" {
		return true
	}
	return false
}

// isGormDefaultModelField Gorm.Model 默认字段
func isGormDefaultModelField(columnName string) bool {
	if columnName == "created_at" ||
		columnName == "updated_at" ||
		columnName == "deleted_at" ||
		columnName == "id" {
		return true
	}
	return false
}

// isGormDefaultModelTimeField Gorm.Model 默认时间字段
func isGormDefaultModelTimeField(columnName string) bool {
	if columnName == "created_at" ||
		columnName == "updated_at" ||
		columnName == "deleted_at" {
		return true
	}
	return false
}

// isDatetime 检查当前列是不是 datetime 类型
func isDatetime(column Columns) bool {
	if mySQLDataType(column.DataType) == MySQLTypeDatetime {
		return true
	}
	return false
}

// isDate 检查当前列是不是 date 类型
func isDate(column Columns) bool {
	if mySQLDataType(column.DataType) == MySQLTypeDate {
		return true
	}
	return false
}
